﻿
namespace CodeSmackdown.Web.Models
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Security;
    using MySql.Data.MySqlClient;

    public partial class CodeSmackdownDataContext
    {
        private Random rand = new Random();

        public int GetVoteCount(User user)
        {
            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                return (from v in csd.Votes
                        where v.UserID == user.UserID
                        select v).Count();
            }
        }

        public List<Snippet> GetSubmittedSnippets(User user)
        {
            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                var snippets = from s in csd.Snippet
                               where s.UserID == user.UserID
                               orderby s.Title ascending
                               select s;
                var results = new List<Snippet>();
                foreach (var snippet in snippets)
                {
                    results.Add(snippet);
                }
                return results;
            }
        }

        public Models.Snippet CreateComment(Snippet snippet, string comment, User currentUser)
        {
            DateTime now = DateTime.Now;
            var cmt = new Comment()
            {
                Body = comment,
                SnippetID = snippet.SnippetID,
                User = currentUser,
                UserID = currentUser.UserID,
                Created = now,
                Modified = now
            };

            using (CodeSmackDown csd = new CodeSmackDown(new MySqlConnection(ConfigSettings.ConnectionStrings.MySQLDatabase)))
            {
                csd.Comments.InsertOnSubmit(cmt);
                csd.SubmitChanges();
            }

            return GetSnippet(snippet.SnippetID);
        }

        public Models.Snippet SaveNewSnippet(string title, string snippet1, string snippet2, User currentUser)
        {
            DateTime now = DateTime.Now;
            Snippet snippet = new Snippet()
            {
                Created = now,
                Modified = now,
                Title = title,
                Snippet1 = snippet1,
                Snippet2 = snippet2,
                UserID = currentUser.UserID
            };

            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                csd.Snippet.InsertOnSubmit(snippet);
                csd.SubmitChanges();
            }
            return snippet;
        }

        private void LoadComments(CodeSmackDown csd, Snippet snippet)
        {
            var users = from u in csd.Users
                        join c in csd.Comments on u.UserID equals c.UserID
                        where c.SnippetID == snippet.SnippetID
                        select u;

            foreach (var comment in snippet.Comments)
            {
                comment.User = users.First(u => u.UserID == comment.UserID);
            }
        }


        public Models.Snippet GetSnippet(int snippetID)
        {
            return GetSnippet(snippetID, true);
        }
        public Models.Snippet GetSnippet(int snippetID, bool loadComments)
        {
            Snippet snippet = null;
            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                var snippets = from s in csd.Snippet
                               where s.SnippetID == snippetID
                               select s;

                if (snippets.Count() > 0)
                {
                    snippet = snippets.First();
                    if (loadComments)
                    {
                        LoadComments(csd, snippet);
                    }
                }
            }
            return snippet;
        }

        public User AddRegularUser(string userName, string password, string email)
        {
            DateTime now = DateTime.Now;
            User user = new User()
            {
                DisplayName = userName,
                Email = email,
                LoginTime = now,
                Created = now,
                Modified = now
            };

            string salt = CreateSalt();
            UserPasswordLogIn login = new UserPasswordLogIn()
            {
                UserName = userName,
                PasswordHash = CreatePasswordHash(password, salt),
                PasswordSalt = salt,
                User = user,
                Created = now,
                Modified = now
            };

            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                // i think we currently have a limitation of the MySQL LINQ provider. I need to get in there
                // and hack it a little more to make sure that the events wire up and that the appropriate
                // values change across relationships. right now, we have to do this in two steps. FAIL!
                csd.Users.InsertOnSubmit(user);
                csd.SubmitChanges();
                login.UserID = user.UserID;
                csd.UserPasswordLogIns.InsertOnSubmit(login);
                csd.SubmitChanges();

                // TODO: fix this issue.
            }

            return user;
        }

        public User ValidateLogin(string userName, string password)
        {
            UserPasswordLogIn user = null;
            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                var users = from u in csd.UserPasswordLogIns
                            where u.UserName == userName
                            select u;

                if (users.Count() > 0)
                {
                    user = users.First();
                }
            }

            if (user != null)
            {
                var hashedPassword = CreatePasswordHash(password, user.PasswordSalt);
                if (hashedPassword.Equals(user.PasswordHash))
                {
                    return UpdateLoginTime(user.UserID);
                }
            }

            return null;
        }

        public User AddOpenIdUser(string nickName, string email, string openIdUri)
        {
            DateTime now = DateTime.Now;
            User user = new User()
            {
                DisplayName = nickName,
                Email = email,
                LoginTime = now,
                Created = now,
                Modified = now
            };

            OpenIDLogIn openIdLogin = new OpenIDLogIn()
            {
                OpenIDUri = openIdUri,
                NickName = nickName,
                User = user,
                Created = now,
                Modified = now
            };

            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                // i think we currently have a limitation of the MySQL LINQ provider. I need to get in there
                // and hack it a little more to make sure that the events wire up and that the appropriate
                // values change across relationships. right now, we have to do this in two steps. FAIL!
                csd.Users.InsertOnSubmit(user);
                csd.SubmitChanges();
                openIdLogin.UserID = user.UserID;
                csd.OpenIDLogIns.InsertOnSubmit(openIdLogin);
                csd.SubmitChanges();

                // TODO: fix this issue.
            }

            return user;
        }

        [AcceptVerbs(HttpVerbs.Get)]
        public User GetUserByUserId(int userID)
        {
            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                var users = from u in csd.Users
                            where u.UserID == userID
                            select u;

                if (users.Count() > 0)
                {
                    return users.First();
                }
            }
            return null;
        }
        

        public User GetUserByOpenId(string openIdUri)
        {
            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                var users = from u in csd.Users
                            join oid in csd.OpenIDLogIns on u.UserID equals oid.UserID
                            where oid.OpenIDUri == openIdUri
                            select u;

                if (users.Count() > 0)
                {
                    return users.First();
                }
            }

            return null;
        }

        public User UpdateLoginTime(string userName)
        {
            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                var users = from u in csd.Users
                            where u.DisplayName == userName
                            select u;

                if (users.Count() > 0)
                {
                    // this only seems to work once per session/app start. Not sure why.
                    // probably another but in the MySQL linq provider
                    User user = users.First();
                    user.LoginTime = DateTime.Now;
                    csd.SubmitChanges();
                    return user;
                }
            }
            return null;
        }

        public User UpdateLoginTime(int userId)
        {
            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                var users = from u in csd.Users
                            where u.UserID == userId
                            select u;

                if (users.Count() > 0)
                {
                    // this only seems to work once per session/app start. Not sure why.
                    // probably another bug in the MySQL linq provider
                    User user = users.First();
                    user.LoginTime = DateTime.Now;
                    csd.SubmitChanges();
                    return user;
                }
            }
            return null;
        }

        public Snippet SubmitVote(User user, Snippet snippet, bool? voteSnippet1)
        {
            DateTime now = DateTime.Now;
            Vote vote = new Vote()
            {
                UserID = user.UserID,
                SnippetID = snippet.SnippetID,
                VoteForSnippet1 = voteSnippet1,
                Created = now,
                Modified = now
            };

            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                csd.Votes.InsertOnSubmit(vote);
                csd.SubmitChanges();

                var result = (from s in csd.Snippet
                              where s.SnippetID == snippet.SnippetID
                              select s).First();

                LoadComments(csd, result);
                return result;
            }
        }

        public Snippet LoadRandomSnippet()
        {
            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                // TODO: make this smarter, do it at the DB Level instead of in memory.
                // This is just temp to make sure that the DB loading works, and that
                // we can view the snippet on screen.
                var results = from s in csd.Snippet
                              select s;

                var size = results.Count();
                var index = rand.Next(0, size);
                var snippet = results.ToArray()[index];
                LoadComments(csd, snippet);
                return snippet;
            }
        }

        private string CreatePasswordHash(string password, string salt)
        {
            var sha = System.Security.Cryptography.HashAlgorithm.Create("SHA256");
            var bytes = new List<byte>(System.Text.Encoding.Unicode.GetBytes(password));
            bytes.AddRange(System.Text.Encoding.Unicode.GetBytes(salt));
            var hash = sha.ComputeHash(bytes.ToArray());
            return Convert.ToBase64String(hash);
        }

        private string CreateSalt()
        {
            System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
            byte[] salt = new byte[10];
            rng.GetNonZeroBytes(salt);
            return Convert.ToBase64String(salt);
        }

        internal List<Comment> GetSubmittedComments(User user)
        {
            using (CodeSmackDown csd = CodeSmackDown.NewContext())
            {
                var comments = from s in csd.Comments 
                               where s.UserID == user.UserID
                               orderby s.Body ascending
                               select s;
                var results = new List<Comment>();
                foreach (var comment in comments)
                {
                    comment.Snippet = GetSnippet(comment.SnippetID, false);
                    results.Add(comment);
                }
                return results;
            }
        }
    }
}